﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Net.Sockets;
using DisruptorUnity3d;
using System;

using MessagePack.Unity;
using MessagePack;

public delegate void MessageReceiveCallback(byte[] msg);
public delegate void MessageWorker(byte[] msg);

public static class MessageProcessor {

	private static Dictionary<int, MessageWorker> messageWorkerDic = new Dictionary<int, MessageWorker>();

	public static void ProcessMessage(byte[] data) {
		if (data != null) {
			//前两个字节是type
			int type = data[0] + (((int)data[1]) << 8);
			byte[] msg = new byte[data.Length - 2];
			Buffer.BlockCopy (data, 2, msg, 0, msg.Length);
			if (messageWorkerDic[type] != null)
				messageWorkerDic [type] (msg);
		}
	}

	public static void RegisterWorker(int type, MessageWorker worker) {
		if (worker != null)
			messageWorkerDic [type] = worker;
	}
}

public class NetworkManager {

	private static NetworkManager _instance;
	private TcpClient client;
	private NetworkStream stream;

	//used to get message from remote and push to RingBuffer,
	//each frame we'll process message in RingBuffer
	private RingBuffer<byte[]> queue = new RingBuffer<byte[]> (64);
	private byte[] msgBufferIn = new byte[64 * 1024 + 2];
	private byte[] msgBufferOut = new byte[64 * 1024 + 2];

	private MessageReceiveCallback messageReceived;

	public static NetworkManager Instance {
		get {
			if (_instance == null) {
				_instance = new NetworkManager ();
			}
			return _instance;
		}
	}

	private NetworkManager() {
	}

	private void Init () {
		AddReceiveListener (MessageProcessor.ProcessMessage);
	}

	public void Run(string host, int port) {

		Init ();

		Connect (host, port);

		if (client != null) {
			ReceiveData ();
		} else {
			Debug.LogError (string.Format("Network cannot run. Is there a running server available? Host: {0}, Port: {1}", host, port));
		}
	}

	private TcpClient Connect(string host, int port) {
		try {
			client = new TcpClient (host, port);

			if (client != null) {
				stream = client.GetStream();
			}
		}catch(Exception e) {
			Debug.Log (e.ToString ());
		}

		return client;
	}

	private void ReceiveData() {
		ReadHeader();
	}

	private void ReadHeader() {
		stream.BeginRead (msgBufferOut, 0, 2, ReadHeaderCallback, null);
	}

	private void ReadHeaderCallback(IAsyncResult result) {
		if (result.IsCompleted) {
			//called to let os release resources
			stream.EndRead (result);

			int size = msgBufferOut [0] + (((int)msgBufferOut [1]) << 8);
			ReadContent (size, size);
		}
	}

	private void ReadContent(int size, object o) {
		stream.BeginRead (msgBufferOut, 0, size, ReadContentCallback, o);
	}

	private void ReadContentCallback(IAsyncResult result) {

		if (result.IsCompleted) {
			//called to let os release resources
			stream.EndRead (result);

			int size = (int) result.AsyncState;
			byte[] data = new byte[size];
			Buffer.BlockCopy (msgBufferOut, 0, data, 0, size);
			Enqueue (data);

			// push the next read operation
			ReadHeader ();
		}
	}

	public void SendDataAsync (byte[] data, int offset, int size, AsyncCallback callback, object param) {
		try {
			stream.BeginWrite(data, offset, size, callback, param);
		}
		catch (Exception e) {
			Debug.Log (e.ToString());
		}
	}

	public void Tick(float deltaTime) {
		//每一帧，我们从queue里面拿出相应的消息进行处理，这个queue解决的是回调线程不能访问主进程（主进程能使用Unity API）的问题
		while (true) {
			if (queue.TryDequeue (out msgBufferIn)) {
				if (messageReceived != null) {
					messageReceived (msgBufferIn);
				}
			} else {
				//没有更多的消息可以处理，退出
				return;
			}
		}
	}

	//从socket接收到消息后，我们把相应的消息enque，所以这里是ReadCallback呼叫的方法
	private void Enqueue(byte[] msg) {
		queue.Enqueue (msg);
	}

	public void AddReceiveListener(MessageReceiveCallback cb) {
		messageReceived += cb;
	}
}
